package com.magic.uschat.util;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.ContextWrapper;
import android.os.Bundle;
import android.os.Looper;
import android.os.MessageQueue;
import android.util.Log;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.inputmethod.InputMethodManager;
import static android.content.Context.INPUT_METHOD_SERVICE;
import static android.os.Build.VERSION.SDK_INT;
import static android.os.Build.VERSION_CODES.KITKAT;;

public class IMMLeaks {
	@SuppressLint("NewApi")
	static class ReferenceCleaner implements MessageQueue.IdleHandler
	,View.OnAttachStateChangeListener,ViewTreeObserver.OnGlobalFocusChangeListener{
		
		private final InputMethodManager inputMethodManager;
		private final Field mHFiled;
		private final Field mServedViewField;
		private final Method finishInputLockedMethod;
		
		
		
		public ReferenceCleaner(InputMethodManager inputMethodManager,
				Field mHFiled, Field mServedViewField,
				Method finishInputLockedMethod) {
			super();
			this.inputMethodManager = inputMethodManager;
			this.mHFiled = mHFiled;
			this.mServedViewField = mServedViewField;
			this.finishInputLockedMethod = finishInputLockedMethod;
		}

		@Override
		public void onGlobalFocusChanged(View oldFocus, View newFocus) {
			if (newFocus==null) {
				return;
			}
			if (oldFocus!=null) {
				oldFocus.removeOnAttachStateChangeListener(this);
			}
			Looper.myQueue().removeIdleHandler(this);
			newFocus.addOnAttachStateChangeListener(this);
		}

		@Override
		public void onViewAttachedToWindow(View v) {
			
		}

		@Override
		public void onViewDetachedFromWindow(View v) {
			v.removeOnAttachStateChangeListener(this);
			Looper.myQueue().removeIdleHandler(this);
			Looper.myQueue().addIdleHandler(this);
		}

		@Override
		public boolean queueIdle() {
			clearInputMethodManagerLeak();
			return false;
		}
		
		private void clearInputMethodManagerLeak(){
			try{
				Object lock = mHFiled.get(inputMethodManager);
				if (null==lock) {
					return;
				}
				// This is highly dependent on the InputMethodManager implementation.
				synchronized (lock) {
					View servedView = (View) mServedViewField.get(inputMethodManager);
					if (null!=servedView) {
						boolean servedViewAttached = servedView.getWindowVisibility()!=View.GONE;
						if (servedViewAttached) {
							 // The view held by the IMM was replaced without a global focus change. Let's make
				              // sure we get notified when that view detaches.

				              // Avoid double registration.
							servedView.removeOnAttachStateChangeListener(this);
							servedView.addOnAttachStateChangeListener(this);
						}else{
							// servedView is not attached. InputMethodManager is being stupid!
							 Activity activity = extractActivity(servedView.getContext());
							 if (null==activity || null==activity.getWindow()) {
								// Unlikely case. Let's finish the input anyways.
					            finishInputLockedMethod.invoke(inputMethodManager);
							}else{
								View decorView = activity.getWindow().peekDecorView();
								boolean windowAttached = decorView.getWindowVisibility()!=View.GONE;
								if (windowAttached) {
									finishInputLockedMethod.invoke(inputMethodManager);
								}else{
									decorView.requestFocusFromTouch();
								}
							}
						}
					}
				}
			}catch (Exception unexpected) {
		        Log.e("IMMLeaks", "Unexpected reflection exception", unexpected);
		     }
			
		}
		
		private Activity extractActivity(Context context){
			while(true){
				if (context instanceof Application) {
					return null;
				}else if(context instanceof Activity){
					return (Activity)context;
				}else if(context instanceof ContextWrapper){
					Context baseContext = ((ContextWrapper)context).getBaseContext();
					if (baseContext==context) {
						return null;
					}
					context = baseContext;
				}else{
					return null;
				}
			}
		}
		
	}
	
	  /**
	   * Fix for https://code.google.com/p/android/issues/detail?id=171190 .
	   *
	   * When a view that has focus gets detached, we wait for the main thread to be idle and then
	   * check if the InputMethodManager is leaking a view. If yes, we tell it that the decor view got
	   * focus, which is what happens if you press home and come back from recent apps. This replaces
	   * the reference to the detached view with a reference to the decor view.
	   *
	   * Should be called from {@link Activity#onCreate(android.os.Bundle)} )}.
	   */
	  @SuppressLint("NewApi")
	public static void fixFocusedViewLeak(Application application) {
		  if (SDK_INT < KITKAT || SDK_INT > 22) {
		      return;
		  }
		  final InputMethodManager inputMethodManager = (InputMethodManager) application.getSystemService(INPUT_METHOD_SERVICE);
		  
		  final Field mServedViewField;
		  final Field mHField;
		  final Method finishInputLockedMethod;
		  final Method focusInMethod;
		  try{
			  mServedViewField = InputMethodManager.class.getDeclaredField("mServedView");
			  mServedViewField.setAccessible(true);
			  mHField = InputMethodManager.class.getDeclaredField("mServedView");
			  mHField.setAccessible(true);
			  finishInputLockedMethod = InputMethodManager.class.getDeclaredMethod("finishInputLocked");
			  finishInputLockedMethod.setAccessible(true);
			  focusInMethod = InputMethodManager.class.getDeclaredMethod("focusIn", View.class);
			  focusInMethod.setAccessible(true);
			  
		  }catch(Exception e){
			  return;
		  }
		  
		  application.registerActivityLifecycleCallbacks(new LifeCycleCallbacksAdapter(){
			  @Override
			public void onActivityCreated(Activity activity,
					Bundle savedInstanceState) {
				  ReferenceCleaner referenceCleaner = new ReferenceCleaner(inputMethodManager, mHField, mServedViewField, finishInputLockedMethod);
				  
				  View rootView = activity.getWindow().getDecorView().getRootView();
				  ViewTreeObserver viewTreeObserver = rootView.getViewTreeObserver();
				  viewTreeObserver.addOnGlobalFocusChangeListener(referenceCleaner);
			}
		  });
		  
	  }
	
	
}
